package com.geekhole.demo.v4.application;

import com.geekhole.demo.domain.Agg;
import com.geekhole.demo.domain.DomainServiceContext;
import com.google.common.base.Preconditions;
import lombok.Value;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.repository.CrudRepository;

import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

public abstract class BaseV4Application {
    private static final Logger LOGGER = LoggerFactory.getLogger(BaseV4Application.class);

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    protected <A extends Agg, CONTEXT extends DomainServiceContext> Creator<A, CONTEXT> creatorFor(CrudRepository<A, ?> repository){
        return new Creator<A, CONTEXT>(repository);
    }

    protected <A extends Agg, CONTEXT extends DomainServiceContext> Updater<A, CONTEXT> updaterFor(CrudRepository<A, ?> aggregateRepository){
        return new Updater<A, CONTEXT>(aggregateRepository);
    }

    protected class Creator<A extends Agg, CONTEXT extends DomainServiceContext>{
        private final CrudRepository<A, ?> aggregateRepository;
        private Function<CONTEXT, A> instanceFun;

        private BiConsumer<A, CONTEXT> successFun = (agg, context)->{
            LOGGER.info("success to handle {} and sync {} to DB", context, agg);
        };

        private BiConsumer<Exception, CONTEXT> errorFun = (exception, context) -> {
            LOGGER.error("failed to handle {}", context, exception);
            if (exception instanceof RuntimeException){
                throw (RuntimeException) exception;
            }
            throw new RuntimeException(exception);
        };

        private BiConsumer<A, CONTEXT> updateFun = (a, context) -> {};

        Creator(CrudRepository<A, ?> aggregateRepository) {
            Preconditions.checkArgument(aggregateRepository != null);
            this.aggregateRepository = aggregateRepository;
        }

        public Creator<A, CONTEXT> instance(Function<CONTEXT, A> instanceFun){
            Preconditions.checkArgument(instanceFun != null);
            this.instanceFun = instanceFun;
            return this;
        }

        public Creator<A, CONTEXT> update(BiConsumer<A, CONTEXT> updater){
            Preconditions.checkArgument(updater != null);
            this.updateFun = this.updateFun.andThen(updater);
            return this;
        }

        public Creator<A, CONTEXT> onSuccess(BiConsumer<A, CONTEXT>  onSuccessFun){
            Preconditions.checkArgument(onSuccessFun != null);
            this.successFun = onSuccessFun.andThen(this.successFun);
            return this;
        }

        public Creator<A, CONTEXT> onError(BiConsumer<Exception, CONTEXT>  errorFun){
            Preconditions.checkArgument(errorFun != null);
            this.errorFun = errorFun.andThen(this.errorFun);
            return this;
        }

        public A call(CONTEXT context){
            Preconditions.checkArgument(this.instanceFun != null, "instance fun can not be null");
            Preconditions.checkArgument(this.aggregateRepository != null, "aggregateRepository can not be null");
            A a = null;
            try{
                a = this.instanceFun.apply(context);

                this.updateFun.accept(a, context);

                this.aggregateRepository.save(a);

                if (eventPublisher != null){
                    // 1. 发布领域事件
                    a.foreachEvent(eventPublisher::publishEvent);

                    // 2. 清理领域事件
                    a.clearEvents();
                }

                this.successFun.accept(a, context);
            }catch (Exception e){
                this.errorFun.accept(e, context);
            }
            return a;
        }
    }

    protected class Updater<A extends Agg, CONTEXT extends DomainServiceContext> {
        private final CrudRepository<A, ?> aggregateRepository;
        private Function<CONTEXT, A> loadFun;
        private Consumer<CONTEXT> onNotExistFun = context -> {};
        private BiConsumer<A, CONTEXT> successFun = (agg, context)->{
            LOGGER.info("success to handle {} and sync {} to DB", context, agg);
        };

        private BiConsumer<Exception, CONTEXT> errorFun = (exception, context) -> {
            LOGGER.error("failed to handle {}", context, exception);
            if (exception instanceof RuntimeException){
                throw (RuntimeException) exception;
            }
            throw new RuntimeException(exception);
        };

        private BiConsumer<A, CONTEXT> updateFun = (a, context) -> {};


        Updater(CrudRepository<A, ?> aggregateRepository) {
            this.aggregateRepository = aggregateRepository;
        }


        public Updater<A, CONTEXT> loader(Function<CONTEXT, A> loader){
            Preconditions.checkArgument(loader != null);
            this.loadFun = loader;
            return this;
        }

        public Updater<A, CONTEXT> update(BiConsumer<A, CONTEXT> updateFun){
            Preconditions.checkArgument(updateFun != null);
            this.updateFun = updateFun.andThen(this.updateFun);
            return this;
        }


        public Updater<A, CONTEXT> onSuccess(BiConsumer<A, CONTEXT>  onSuccessFun){
            Preconditions.checkArgument(onSuccessFun != null);
            this.successFun = onSuccessFun.andThen(this.successFun);
            return this;
        }

        public Updater<A, CONTEXT> onError(BiConsumer<Exception, CONTEXT>  errorFun){
            Preconditions.checkArgument(errorFun != null);
            this.errorFun = errorFun.andThen(this.errorFun);
            return this;
        }


        public Updater<A, CONTEXT> onNotFound(Consumer<CONTEXT>  onNotExistFun){
            Preconditions.checkArgument(onNotExistFun != null);
            this.onNotExistFun = onNotExistFun.andThen(this.onNotExistFun);
            return this;
        }

        public A call(CONTEXT context){
            Preconditions.checkArgument(this.aggregateRepository != null, "aggregateRepository can not be null");
            Preconditions.checkArgument(this.loadFun != null, "loader can not both be null");
            A a = null;
            try {
                a = this.loadFun.apply(context);

                if (a == null){
                    this.onNotExistFun.accept(context);
                }

                updateFun.accept(a, context);


                this.aggregateRepository.save(a);

                if (eventPublisher != null){
                    // 1. 发布领域事件
                    a.foreachEvent(eventPublisher::publishEvent);

                    // 2. 清理领域事件
                    a.clearEvents();
                }

                this.successFun.accept(a, context);

            }catch (Exception e){
                this.errorFun.accept(e, context);
            }
            return a;
        }

    }
}
